package com.tf_cut.backend.request;

import java.io.StringReader;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.codec.binary.Base64;
import org.apache.xml.security.signature.XMLSignature;


import org.w3c.dom.Document;
import org.xml.sax.InputSource;

import com.tf_cut.backend.request.util.SignatureUtils;
import com.tf_cut.backend.request.util.StringUtils;
import com.tf_cut.backend.request.util.XmlSignatureAppendMode;
import com.tf_cut.backend.seller.enums.MybankApiExceptionEnum;
import com.tf_cut.backend.util.MybankConstants;

public class MybankSignature {


    /**
     * WEB签名
     *
     * @param content    待签名参数字符串
     * @param privateKey 签名私钥
     * @param signType   签名类型 支持RSA
     * @param charset    字符编码 UTF-8
     * @return
     * @throws MybankApiException
     */
    public static String rsa256SignContent(String content, String privateKey, String signType, String charset) throws MybankApiException {
        if (!MybankConstants.SIGN_TYPE_RSA.equals(signType)) {
            throw new MybankApiException(MybankApiExceptionEnum.ILLEGAL_SIGN_TYPE);
        }
        try {
            PrivateKey priKey = SignatureUtils.getPrivateKey(privateKey, signType);
            java.security.Signature signature = java.security.Signature.getInstance(MybankConstants.SIGN_SHA256RSA_ALGORITHMS);
            signature.initSign(priKey);
            if (StringUtils.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }
            return Base64.encodeBase64String(signature.sign());
        } catch (Exception e) {
            throw new MybankApiException(MybankApiExceptionEnum.SDK_ENCRYPT_ERROR, e);
        }
    }

    /**
     * WEB验签
     *
     * @param content   待验签参数字符串
     * @param sign      签名值
     * @param publicKey 验签公钥
     * @param signType  签名类型 支持RSA
     * @param charset   字符编码 UTF-8
     * @return
     * @throws MybankApiException
     */
    public static boolean rsa256CheckContent(String content, String sign, String publicKey, String signType, String charset) throws MybankApiException {
        if (!MybankConstants.SIGN_TYPE_RSA.equals(signType)) {
            throw new MybankApiException(MybankApiExceptionEnum.ILLEGAL_SIGN_TYPE);
        }
        try {
            PublicKey pubKey = SignatureUtils.getPublicKey(publicKey, signType);
            java.security.Signature signature = java.security.Signature.getInstance(MybankConstants.SIGN_SHA256RSA_ALGORITHMS);
            signature.initVerify(pubKey);
            if (StringUtils.isEmpty(charset)) {
                signature.update(content.getBytes());
            } else {
                signature.update(content.getBytes(charset));
            }
            return signature.verify(Base64.decodeBase64(sign));
        } catch (Exception e) {
            throw new MybankApiException(MybankApiExceptionEnum.SDK_VERIFY_ERROR, e);
        }
    }

    /**
     * XML签名
     *
     * @param xmlContent 待签名XML字符串
     * @param privateKey 签名私钥
     * @param charset    字符编码 UTF-8
     * @param signType   签名类型 支持RSA
     * @return
     * @throws MybankApiException
     */
    public static String sign(String xmlContent, String privateKey, String charset,
                              String signType, String elementTagName) throws MybankApiException {
        if (MybankConstants.SIGN_TYPE_RSA.equals(signType)) {
            return rsaSign(xmlContent, privateKey, charset, signType, elementTagName);
        } else {
            throw new MybankApiException(MybankApiExceptionEnum.ILLEGAL_SIGN_TYPE);
        }
    }

    private static String rsaSign(String xmlContent, String privateKey, String charset, String signType, String elementTagName) throws MybankApiException {
        try {
            PrivateKey priKey = SignatureUtils.getPrivateKey(privateKey, signType);
            Document xmlDoc = parseDocumentByString(xmlContent);
            return SignatureUtils.signXmlElement(priKey, xmlDoc, elementTagName,
                    XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA256, XmlSignatureAppendMode.AS_BROTHER);
        } catch (InvalidKeySpecException e) {
            throw new MybankApiException(MybankApiExceptionEnum.ILLEGAL_RSA_PRIVATE_KEY, e);
        } catch (Exception e) {
            throw new MybankApiException(MybankApiExceptionEnum.SDK_ENCRYPT_ERROR, e);
        }
    }

    /**
     * XML验签
     *
     * @param xmlContent 待验签XML字符串
     * @param publicKey  验签公钥
     * @param charset    字符编码 UTF-8
     * @param signType   签名类型 支持RSA
     * @return
     * @throws MybankApiException
     */
    public static boolean check(String xmlContent, String publicKey, String charset, String signType) throws MybankApiException {
        if (MybankConstants.SIGN_TYPE_RSA.equals(signType)) {
            return rsaCheckContent(xmlContent, publicKey, charset, signType);
        } else {
            throw new MybankApiException(MybankApiExceptionEnum.ILLEGAL_SIGN_TYPE);
        }
    }

    private static boolean rsaCheckContent(String xmlContent, String publicKey, String charset, String signType) throws MybankApiException {
        try {
            PublicKey pubKey = SignatureUtils.getPublicKey(publicKey, signType);
            Document xmlDoc = parseDocumentByString(xmlContent);
            return SignatureUtils.verifyXmlElement(pubKey, xmlDoc);
        } catch (Exception e) {
            throw new MybankApiException(MybankApiExceptionEnum.SDK_VERIFY_ERROR, e);
        }
    }

    public static Document parseDocumentByString(String xmlContent) throws MybankApiException {
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true);
            return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xmlContent)));
        } catch (Exception e) {
            throw new MybankApiException(MybankApiExceptionEnum.XML_PARSE_ERROR, e);
        }
    }
}